﻿using System;
using System.Collections.Generic;

namespace Adventure.Server.Library
{
    /// <summary>
    /// Attributes of an object.
    /// </summary>
    public class ObjectAttributes
    {
        #region Fields

        /// <summary>
        /// Attributes
        /// </summary>
        private readonly Dictionary<string, string> _attributes;

        #endregion

        #region Lifetime

        /// <summary>
        /// Creates a new instance of ObjectAttributes
        /// </summary>
        public ObjectAttributes()
        {
            _attributes = new Dictionary<string, string>();
        }

        /// <summary>
        /// Creates a new instance of ObjectAttributes
        /// </summary>
        /// <param name="attributes">Some attributes to initialize.</param>
        public ObjectAttributes(Dictionary<string, string> attributes)
            : this()
        {
            // Validate arguments
            if (attributes == null) throw new ArgumentNullException(nameof(attributes));

            foreach (var element in attributes)
                _attributes.Add(element.Key, element.Value);
        }

        /// <summary>
        /// Get an Enumerator for all Object Attributes.
        /// </summary>
        /// <returns>The enumerator over all Attributes.</returns>
        public Dictionary<string,string>.Enumerator GetEnumerator()
        {
            return _attributes.GetEnumerator();
        }

        #endregion

        #region Operators

        /// <summary>
        /// Tests two objects of this type for equality by value.
        /// </summary>
        public static bool operator ==(ObjectAttributes attrib1, ObjectAttributes attrib2)
        {
            // If both are null, or both are same instance, return true.
            if (ReferenceEquals(attrib1, attrib2))
                return true;

            // If left hand side is null return false.
            if (ReferenceEquals(attrib1, null))
                return false;

            // Return compare of both objects.
            return attrib1.Equals(attrib2);
        }

        /// <summary>
        /// Tests two objects of this type for inequality by value.
        /// </summary>
        public static bool operator !=(ObjectAttributes attrib1, ObjectAttributes attrib2)
        {
            return !(attrib1 == attrib2);
        }

        /// <summary>
        /// Compares this object with another by value.
        /// </summary>
        public override bool Equals(object obj)
        {
            // Compare nullability and type
            var other = obj as ObjectAttributes;
            if (ReferenceEquals(other, null))
                return false;

            // Check the number of elements
            if (_attributes.Count != other._attributes.Count)
                return false;

            // Check the elements
            foreach (var key in _attributes.Keys)
            {
                if (!other._attributes.ContainsKey(key))
                    return false;

                if (_attributes[key] != other._attributes[key])
                    return false;
            }

            // All elements was equal
            return true;
        }

        /// <summary>
        /// Returns a hashcode based on the current value of this object.
        /// </summary>
        public override int GetHashCode()
        {
            int hashCode = 0;
            foreach (var element in _attributes)
            {
                hashCode ^= element.Key.GetHashCode() ^ (element.Value ?? "").GetHashCode();
            }
            return hashCode;
        }

        #endregion

        #region Properties

        /// <summary>
        /// Indexer to quickly access the attribute.
        /// </summary>
        /// <param name="key">Key of the attribute.</param>
        /// <returns>Value stored for that key or null.</returns>
        public string this[string key]
        {
            get { return _attributes.ContainsKey(key) ? _attributes[key] : null;  }
            set { _attributes[key] = value; }
        }

        /// <summary>
        /// Gets an attribute as string.
        /// </summary>
        /// <param name="key">Key of the attribute </param>
        /// <returns>Value of the attribute as string.</returns>
        public string GetAttributeAsString(string key)
        {
            return this[key];
        }

        /// <summary>
        /// Gets an attribute as Integer.
        /// </summary>
        /// <param name="key">Key of the attribute </param>
        /// <returns>Value of the attribute as int or null.</returns>
        public int? GetAttributeAsInt(string key)
        {
            int value;
            return int.TryParse(this[key], out value) ? value : (int?) null;
        }

        /// <summary>
        /// Gets an attribute as long.
        /// </summary>
        /// <param name="key">Key of the attribute </param>
        /// <returns>Value of the attribute as long or null.</returns>
        public long? GetAttributeAsLong(string key)
        {
            long value;
            return long.TryParse(this[key], out value) ? value : (long?)null;
        }

        /// <summary>
        /// Gets an attribute as bool.
        /// </summary>
        /// <param name="key">Key of the attribute or null.</param>
        /// <returns>Value of the attribute as bool or null.</returns>
        public bool? GetAttributeAsBool(string key)
        {
            bool value;
            return bool.TryParse(this[key], out value) ? value : (bool?) null;
        }

        /// <summary>
        /// Sets an attribute
        /// </summary>
        /// <param name="key">Key of the attribute</param>
        /// <param name="value">Value to set.</param>
        public void SetAttribute(string key, string value)
        {
            this[key] = value;
        }

        /// <summary>
        /// Sets an attribute
        /// </summary>
        /// <param name="key">Key of the attribute</param>
        /// <param name="value">Value to set.</param>
        public void SetAttribute(string key, int value)
        {
            this[key] = value.ToString();
        }

        /// <summary>
        /// Sets an attribute
        /// </summary>
        /// <param name="key">Key of the attribute</param>
        /// <param name="value">Value to set.</param>
        public void SetAttribute(string key, long value)
        {
            this[key] = value.ToString();
        }

        /// <summary>
        /// Sets an attribute
        /// </summary>
        /// <param name="key">Key of the attribute</param>
        /// <param name="value">Value to set.</param>
        public void SetAttribute(string key, bool value)
        {
            this[key] = value.ToString();
        }
        
        #endregion
    }
}
